---
title: 元件
description: .astro 元件語法簡介。
i18nReady: true
---

**Astro 元件**是 Astro 專案的基石，只包含 HTML 模板，不依賴客戶端運行環境。你可以從副檔名辨別它：`.astro`

Astro 元件極其彈性，能包含**共用 UI**（像是頁首、個人資訊卡）、一小段 HTML（例如跟 SEO 有關的 `<meta>` 標籤），甚至整個網頁版面。

Astro 元件最重要的一點是：**不在客戶端渲染**。它們只在建置階段或[伺服器端渲染（SSR）](/zh-tw/guides/server-side-rendering/)需要時轉換為 HTML。元件的 frontmatter 可放入 JavaScript 程式碼，它們不會送至使用者的瀏覽器。預設不包含任何 JavaScript 足跡，因此你的網站變得更快了。

如果需要客戶端的互動效果，可適時加入 [HTML `<script>` 標籤](/zh-tw/guides/client-side-scripts/)或 [UI 框架元件](/zh-tw/guides/framework-components/#hydrating-interactive-components)。


## 元件架構

Astro 元件由兩塊組成：**元件腳本**和**元件模板**。兩者各司其職，攜手構築出既容易使用，又能滿足多樣開發需求的框架。

```astro title="src/components/EmptyComponent.astro"
---
// 元件腳本（JavaScript）
---
<!-- 元件模板（HTML + JS 表達式） -->
```

### 元件腳本

Astro 使用「程式碼圍欄」（`---`）區別元件腳本。如果你曾寫過 Markdown，你可能知道 *frontmatter*，兩者是相似的概念。Astro 元件腳本的構想就是受此概念啟發。

你可在元件腳本中撰寫渲染模板時需要用到的 JavaScript 程式碼。包含：

- 匯入其他 Astro 元件
- 匯入其他框架元件，例如 React
- 匯入資料，例如 JSON 檔
- 從 API 或資料庫抓取資料
- 建立變數供模板使用


```astro title="src/components/MyComponent.astro"
---
import SomeAstroComponent from '../components/SomeAstroComponent.astro';
import SomeReactComponent from '../components/SomeReactComponent.jsx';
import someData from '../data/pokemon.json';

// 存取傳入元件的參數，例如 `<X title="Hello, World" />`
const { title } = Astro.props;
// 抓取外部資料，從非公開 API 或資料庫都可以
const data = await fetch('SOME_SECRET_API_URL/users').then(r => r.json());
---
<!-- 模板在這！ -->
```

程式碼圍欄的設計理念是要「圍住」你寫的 JavaScript，確保它不會跑到前端應用程式、落入使用者手中。在圍欄裡可以安心地寫較耗資源或者敏感的程式碼（例如呼叫你的私有資料庫），不必擔心裡面的程式碼不小心出現在使用者的瀏覽器裡。

:::tip
元件腳本裡甚至可以寫 TypeScript！
:::

### 元件模板

元件模板位於程式碼圍欄下方，它決定了最終產出的 HTML。

寫在這裡的 HTML 會被元件渲染，當元件被其他 Astro 頁面匯入使用時亦同。

不只一般的 HTML，[Astro 的元件模板語法](/zh-tw/basics/astro-syntax/) 也支援 **JavaScript 表達式**、Astro [`<style>`](/zh-tw/guides/styling/#styling-in-astro) 和 [`<script>`](/zh-tw/guides/client-side-scripts/#using-script-in-astro) 標籤、**匯入的元件**，以及[**特殊的 Astro 指令**](/zh-tw/reference/directives-reference/)。

```astro title="src/components/MyFavoritePokemon.astro"
---
// 這裡是元件腳本！
import Banner from '../components/Banner.astro';
import ReactPokemonComponent from '../components/ReactPokemonComponent.jsx';
const myFavoritePokemon = [/* ... */];
const { title } = Astro.props;
---
<!-- 支援 HTML 註解！ -->
{/* JS 註解也沒問題！ */}

<Banner />
<h1>Hello, world!</h1>

<!-- 使用 props 和元件腳本的變數： -->
<p>{title}</p>

<!-- 透過 `client:` 指令引入其他 UI 框架元件：-->
<ReactPokemonComponent client:visible />

<!-- 在 HTML 使用 JavaScript 表達式，類似 JSX：-->
<ul>
  {myFavoritePokemon.map((data) => <li>{data.name}</li>)}
</ul>

<!-- 使用模板指令合併 class name，字串甚至是物件都可以！ -->
<p class:list={["add", "dynamic", {classNames: true}]} />
```

## 以元件為基礎的設計

元件的設計理念是要能夠**重複使用**和**組合**。你可以在元件裡使用其他元件，創造出更多更複雜的 UI。舉例來說，`Button` 元件可以組合成 `ButtonGroup` 元件：

```astro title="src/components/ButtonGroup.astro"
---
import Button from './Button.astro';
---
<div>
  <Button title="Button 1" />
  <Button title="Button 2" />
  <Button title="Button 3" />
</div>
```


## 元件參數

Astro 元件可以自訂和接受參數，供元件模板渲染 HTML 時使用。參數可以在 frontmatter 腳本透過全域的 `Astro.props` 存取。

以下範例中，元件接受傳入的 `greeting` 和 `name` 參數。注意參數是從全域的 `Astro.props` 物件解構出來的。

```astro "Astro.props"
---
// src/components/GreetingHeadline.astro
// 用法：<GreetingHeadline greeting="Howdy" name="Partner" />
const { greeting, name } = Astro.props;
---
<h2>{greeting}, {name}!</h2>
```

其他 Astro 元件／版面／頁面，可以傳遞參數給 `GreetingCard` 元件：

```astro /(\w+)=\S+/
---
// src/components/GreetingCard.astro
import GreetingHeadline from './GreetingHeadline.astro';
const name = 'Astro';
---
<h1>歡迎卡片</h1>
<GreetingHeadline greeting="Hi" name={name} />
<p>祝你有個美好的一天！</p>
```

你可以用 TypeScript 自訂 `Props` 的型別，Astro 能自動偵測 frontmatter 的 `Props`，依此顯示警告／錯誤訊息。另外，從 `Astro.props` 解構參數時，能夠設定預設值。

```astro ins={3-6}
---
// src/components/GreetingHeadline.astro
interface Props {
  name: string;
  greeting?: string;
}

const { greeting = "Hello", name } = Astro.props;
---
<h2>{greeting}, {name}!</h2>
```

未傳入參數值時，可以設定預設值。

```astro ins="= \"Hello\"" ins="= \"Astronaut\""
---
// src/components/GreetingHeadline.astro
const { greeting = "Hello", name = "Astronaut" } = Astro.props;
---
<h2>{greeting}, {name}!</h2>
```

## 插槽

`<slot />` 元素是為外部 HTML 內容預留的位置，供子元素從其他檔案放入元件模板用。

預設情況下，元件會渲染所有透過 `<slot />` 傳入的子元素。

:::note
_參數_ 是傳遞給 Astro 元件的屬性，可以在元件中透過 `Astro.props` 存取，而 _插槽_ 則是在它們被寫下的位置渲染 HTML 子元素。
:::

```astro "<slot />"
---
// src/components/Wrapper.astro
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';

const { title } = Astro.props;
---
<div id="content-wrapper">
  <Header />
  <Logo />
  <h1>{title}</h1>
  <slot />  <!-- 子元素會跑到這 -->
  <Footer />
</div>
```

```astro {6-7}
---
// src/pages/fred.astro
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="Fred's Page">
  <h2>關於 Fred</h2>
  <p>關於 Fred 的一些資訊。</p>
</Wrapper>
```

一整頁 HTML 內容被 `<SomeLayoutComponent></SomeLayoutComponent>` 標籤「包起來」，接著傳給其他元件渲染頁面所有元素——這個模式便是 [Astro 版面元件](/zh-tw/basics/layouts/)的基礎。



### 具名插槽

Astro 元件可以放置具名插槽，讓你只傳入名字相符的 HTML 元素到指定插槽。

具名插槽使用 `name` 屬性：

```astro /<slot .*?/>/
---
// src/components/Wrapper.astro
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';

const { title } = Astro.props;
---
<div id="content-wrapper">
  <Header />
  <slot name="after-header" />  <!-- 有 `slot="after-header"` 屬性的子元素會跑來這 -->
  <Logo />
  <h1>{title}</h1>
  <slot />  <!-- 沒有 `slot` 屬性的子元素，以及有 `slot="default"` 屬性的子元素會跑來這 -->
  <Footer />
  <slot name="after-footer" />  <!-- 有 `slot="after-footer"` 屬性的子元素會跑來這 -->
</div>
```

要把 HTML 內容放進特定的插槽，必須在子元素加上 `slot` 屬性指定對應的插槽，所有其他子元素則會被放入預設（不具名）的 `<slot />`。

```astro /slot=".*?"/
---
// src/pages/fred.astro
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="Fred's Page">
  <img src="https://my.photo/fred.jpg" slot="after-header" />
  <h2>關於 Fred</h2>
  <p>關於 Fred 的一些資訊。</p>
  <p slot="after-footer">版權所有 2022</p>
</Wrapper>
```

:::tip
在子元素加上 `slot="my-slot"`，藉此將它們傳遞到對應的 `<slot name="my-slot" />` 預留位置。
:::

若要一次傳遞多個 HTML 元素，但不想額外包一層 `<div>` 的話，可以在 [Astro 的 `<Fragment/>` 元件](/zh-tw/reference/api-reference/#fragment-)使用 `slot=""`：

```astro title="src/components/CustomTable.astro" "<slot name="header"/>" "<slot name="body"/>"
---
// 客製一個 table，使用具名插槽為 head 和 body 預留兩個位置
---
<table class="bg-white">
  <thead class="sticky top-0 bg-white"><slot name="header" /></thead>
  <tbody class="[&_tr:nth-child(odd)]:bg-gray-100"><slot name="body" /></tbody>
</table>
```

透過 `slot=""` 屬性，將表格內容放置於 `"header"` 和 `"body"`。還能單獨調整 HTML 元素的樣式。

```astro title="src/components/StockTable.astro" {5-7, 9-13} '<Fragment slot="header">' '<Fragment slot="body">'
---
import CustomTable from './CustomTable.astro';
---
<CustomTable>
  <Fragment slot="header"> <!-- 表首會傳到這 -->
    <tr><th>產品名稱</th><th>庫存量</th></tr>
  </Fragment>

  <Fragment slot="body"> <!-- 內容會傳到這 -->
    <tr><td>夾腳拖</td><td>64</td></tr>
    <tr><td>靴子</td><td>32</td></tr>
    <tr><td>球鞋</td><td class="text-red-500">0</td></tr>
  </Fragment>
</CustomTable>
```

只能透過具名插槽傳送一層子元素，不能傳送巢狀元素，請特別留意。

:::tip
具名插槽也可傳遞到 [UI 框架元件](/zh-tw/guides/framework-components/)！
:::


:::note
Astro 插槽名稱無法動態產生（例如透過 map 函式操作）。想在 UI 框架元件裡動態產生插槽名稱的話，最好還是讓框架本身去處理。
:::


### 插槽的備用內容

插槽也能渲染**備用內容**。沒有對應的子元素傳遞過來時，`<slot />` 元素會渲染自己的備用內容。

```astro {14}
---
// src/components/Wrapper.astro
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';

const { title } = Astro.props;
---
<div id="content-wrapper">
  <Header />
  <Logo />
  <h1>{title}</h1>
  <slot>
    <p>這是我的備用內容，如果沒有傳子元素到 slot 時會顯示。</p>
  </slot>
  <Footer />
</div>
```

### 轉移插槽

插槽可以轉移到其他元件。以建立巢狀版面為例：

```astro title="src/layouts/BaseLayout.astro" {9,12}
---
---
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
		<meta name="viewport" content="width=device-width" />
		<meta name="generator" content={Astro.generator} />
    <slot name="head" />
	</head>
	<body>
		<slot />
	</body>
</html>
```

```astro {6,7}
// src/layouts/HomeLayout.astro
---
import BaseLayout from './BaseLayout.astro';
---
<BaseLayout>
  <slot name="head" slot="head" />
  <slot />
</BaseLayout>
```

:::note
`name` 和 `slot` 屬性都可將具名插槽轉移至其他元件。
:::

傳給 `HomeLayout` 的預設插槽和 `head` 插槽，現在會被轉移到上層的 `BaseLayout`。

```astro
// src/pages/index.astro
---
import HomeLayout from '../layouts/HomeLayout.astro';
---
<HomeLayout>
	<title slot="head">Astro</title>
	<h1>Astro</h1>
</HomeLayout>
```



## HTML 元件

Astro 支援將 `.html` 檔案匯入成元件，或放置到 `src/pages` 子目錄裡當作頁面。如果你想在不使用框架的前提下重複利用現有網站程式碼，或想確保元件沒有動態功能的話，可以考慮使用 HTML 元件。

HTML 元件只能包含合法的 HTML，沒有 Astro 元件的特色功能：

- 不支援 frontmatter，伺服器端匯入，以及動態表達式
- `<script>` 標籤不會被打包，行為同 Astro 的 `is:inline` 屬性
- 只能[連結 `public/` 資料夾裡的靜態資源](/zh-tw/basics/project-structure/#public)

:::note
HTML 元件裡的 [`<slot />` 元素](/zh-tw/basics/astro-components/#插槽)可以正常運作。如果想用 [HTML Web Component 插槽](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) 元素的話，在 `<slot>` 元素加上 `is:inline`。
:::

## 下一步

import ReadMore from '~/components/ReadMore.astro';

<ReadMore>繼續閱讀如何在 Astro 專案使用 [UI 框架元件](/zh-tw/guides/framework-components/)。</ReadMore>
